package com.example.myapplication.geofence

import android.annotation.SuppressLint
import android.app.*
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.location.Location
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.lifecycle.LifecycleService
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.example.myapplication.BuildConfig
import com.example.myapplication.R
import com.example.myapplication.geolocation.GeoLocationCallback
import com.example.myapplication.geolocation.GoogleGeolocation
import com.example.myapplication.geolocation.IBaseGeoLocation
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.GeofenceStatusCodes

/**
 * geofence or location foreground service
 */
class GeofenceLocationService : LifecycleService(), GeoLocationCallback {
    private val TAG = GeofenceLocationService::class.java.simpleName

    companion object {
        public const val CHANNEL_ID = BuildConfig.APPLICATION_ID
        public const val ACTION_BROADCAST = "${BuildConfig.APPLICATION_ID}.locationupdatesforegroundservice.broadcast"
        public const val EXTRA_LOCATION = "Location"
        const val ACTION_STOP_UPDATES = BuildConfig.APPLICATION_ID + ".ACTION_STOP_UPDATES"

    }


    private val mBinder: IBinder = LocalBinder()

    lateinit var mGeofenceManager: GeofenceManager
    lateinit var mGeoLocation: IBaseGeoLocation

    //    private val geofenceList: ArrayList<Geofence> = ArrayList()
    private var started = false
    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "onCreate")
        createNotificationChannel()
        initGeofencingClient()
        initGeolocation()
    }

    override fun onDestroy() {
        super.onDestroy()
        mGeoLocation.onStopLocation(this)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)
        Log.i(TAG, "onStartCommand")
        if (intent?.action == ACTION_STOP_UPDATES) {
            stopLocationUpdates()
        }

        if (!started) {
            started = true
        }
        manageForeground()
        return START_STICKY
    }

    private fun initGeofencingClient() {
        mGeofenceManager = GeofenceManager.newInstance().initGeofencingClient(this)
    }

    private fun initGeolocation() {
        mGeoLocation = GoogleGeolocation.instance.initLocationClient(this)
        startLocationUpdates()
    }

    public fun getGeofencing(
        latitude: Double,
        longitude: Double,
        radius: Float,
        key: String,
        transitionTypes: Int,
        mLoiteringDelay: Int? = null
    ): Geofence {
        return mGeofenceManager.getGeofencing(latitude, longitude, radius, key, transitionTypes, mLoiteringDelay)

    }

    @SuppressLint("MissingPermission")
    public fun startGeofencing(geofenceList: ArrayList<Geofence>) {
        mGeofenceManager.startGeofencing(this, geofenceList)?.run {
            addOnSuccessListener {
                Log.i(TAG, "addOnSuccessListener  size = ${geofenceList.size}")
            }
            addOnFailureListener {
                Log.e(TAG, "addOnFailureListener")
                it.printStackTrace()
                if (it is ApiException) {
                    when (it.statusCode) {
                        GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE -> {
                            Log.e(
                                TAG,
                                "Geofence service is not available now. Typically this is because the user turned off location access in settings > location access."
                            )
                        }
                        GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES -> {
                            Log.e(TAG, "Your app has registered more than 100 geofences. Remove unused ones before adding new geofences.")
                        }
                        GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS -> {
                            Log.e(TAG, "You have provided more than 5 different PendingIntents to the")
                        }
                    }
                }
            }
        }
    }

    public fun startGeofencing(mGeofence: Geofence) {
        startGeofencing(arrayListOf(mGeofence))
    }

    public fun stopAllGeofencing() {
        mGeofenceManager.stopAllGeofencing(this)
    }

    public fun stopGeofencing(key: String) {
        mGeofenceManager.stopGeofencing(key)
    }


    override fun onBind(intent: Intent): IBinder? {
        super.onBind(intent)
        Log.i(TAG, "onBind")
        handleBind()
        return mBinder
    }

    override fun onRebind(intent: Intent?) {
        Log.i(TAG, "onRebind")
        handleBind()
    }

    private var bindCount = 0
    private fun isBound() = bindCount > 0
    private fun handleBind() {
        bindCount++
        startService(Intent(this, this::class.java))
    }

    private val NOTIFICATION_ID = 1234

    override fun onUnbind(intent: Intent?): Boolean {
        bindCount--
        manageForeground()
        Log.i(TAG, "onUnbind")
        return true

    }

    private fun manageForeground() {
        when {
            isBound() -> exitForeground()
            else -> enterForeground()
        }
    }

    private var isForeground = false

    private fun exitForeground() {
        if (isForeground) {
            isForeground = false
            stopForeground(true)
        }
    }

    private fun enterForeground() {
        if (!isForeground) {
            isForeground = true

            startForeground(NOTIFICATION_ID, buildNotification(mLocation))
        }
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationChannel = NotificationChannel(
                CHANNEL_ID,
                getString(R.string.notification_channel_name),
                NotificationManager.IMPORTANCE_DEFAULT
            )
            val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
            manager.createNotificationChannel(notificationChannel)
        }
    }

    private fun buildNotification(location: Location?): Notification {
        val pendingIntent = PendingIntent.getActivity(
            this,
            0,
            packageManager.getLaunchIntentForPackage(this.packageName),
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
        )
        val stopIntent = PendingIntent.getService(
            this,
            0,
            Intent(this, this::class.java).setAction(ACTION_STOP_UPDATES),
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
        )
        val contentText = if (location != null) {
            getString(R.string.location_lat_lng, location.latitude, location.longitude)
        } else {
            getString(R.string.waiting_for_location)
        }

        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle(getString(R.string.notification_title))
            .setContentText(contentText)
            .setContentIntent(pendingIntent)
            .setSmallIcon(R.mipmap.ic_launcher)
            .addAction(R.mipmap.ic_launcher, getString(R.string.stop), stopIntent)
            .setOngoing(true)
            .setCategory(NotificationCompat.CATEGORY_SERVICE)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
            .build()
    }

    inner class LocalBinder : Binder() {
        val service: GeofenceLocationService
            get() = this@GeofenceLocationService
    }

    fun serviceIsRunningInForeground(context: Context): Boolean {
        val manager = context.getSystemService(
            Context.ACTIVITY_SERVICE
        ) as ActivityManager
        for (service in manager.getRunningServices(Int.MAX_VALUE)) {
            if (javaClass.name == service.service.className) {
                if (service.foreground) {
                    return true
                }
            }
        }
        return false
    }

    fun startLocationUpdates() {
        mGeoLocation.onStartLocation(this, this)
    }

    fun stopLocationUpdates() {
        mGeoLocation.onStopLocation(this)
    }

    override fun onLocationChanged(location: Location) {
        onNewLocation(location)
    }

    private var mLocation: Location? = null

    private fun onNewLocation(location: Location) {
        Log.i(TAG, "New location ${javaClass.name}: $location")
        mLocation = location
        val intent = Intent(ACTION_BROADCAST)
        intent.putExtra(EXTRA_LOCATION, location)
        LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
        if (serviceIsRunningInForeground(this)) {
            (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).notify(NOTIFICATION_ID, buildNotification(location));
        }
    }


}

open class ForegroundGeofenceLocationServiceConnection : ServiceConnection {

    var service: GeofenceLocationService? = null
        private set

    override fun onServiceConnected(name: ComponentName, binder: IBinder) {
        service = (binder as GeofenceLocationService.LocalBinder).service
    }

    override fun onServiceDisconnected(name: ComponentName) {
        service = null
    }
}